/*---------------------------------------------------------------------------*\
    Attributed Grammar for Coco/R  (-*- C++ -*- version)
    compile with:
        coco-cpp wmkdependParser.atg
\*---------------------------------------------------------------------------*/
[copy]
/*---------------------------------*- C++ -*---------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     |
    \\  /    A nd           | Copyright (C) 2011 OpenFOAM Foundation
     \\/     M anipulation  |
-------------------------------------------------------------------------------
License
    This file is part of OpenFOAM.

    OpenFOAM is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    for more details.

    You should have received a copy of the GNU General Public License
    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.

@file wmkdependParser.atg

Description
    An attributed Coco/R grammar to parse C/C++, Fortran and Java files
    for include and import statements.

SourceFiles
    generated

\*---------------------------------------------------------------------------*/
[/copy]
#include <iostream>
#include <string>
#include <list>
#include <set>

/*---------------------------------------------------------------------------*/

COMPILER wmkdepend
    // grammar pragmas:
    $namespace=wmake
    $prefix=wmkdepend
    $define=FORCE_UTF8

/*---------------------------------------------------------------------------*/
private:

    //! Set of (java) directories already visited
    static std::set<std::string> visitedDirs_;

    //! Replace all '.' with '/'
    static void dotToSlash(std::string& name);

    //! Import (java) directories
    static void importDir(const std::string& dirName);

    //! Import (java) file
    static void importFile(const std::string& name);

public:
    //! Set of files already visited
    static std::set<std::string> visitedFiles;

    //! Include directories to search
    static std::list<std::string> includeDirs;

    //! The name of the top-level source file
    static std::string sourceFile;

    //! The name of the top-level dep file
    static std::string depFile;

    //! Add directory to list of visited dirs, thus effectively ignoring it
    static void ignoreDir(const std::string& name);

    //! Include file
    static void includeFile(const std::string& name);

/*---------------------------------------------------------------------------*/
[code]
#include <sys/types.h>
#include <dirent.h>

std::set<std::string> Parser::visitedDirs_;

std::set<std::string> Parser::visitedFiles;
std::list<std::string> Parser::includeDirs;
std::string Parser::sourceFile;
std::string Parser::depFile;


void Parser::dotToSlash(std::string& name)
{
    std::string::size_type start = 0;

    while ((start = name.find('.', start)) != std::string::npos)
    {
        name.replace(start, 1, 1, '/');
        start++;
    }
}


void Parser::ignoreDir(const std::string& name)
{
    visitedDirs_.insert(name);
}


void Parser::includeFile(const std::string& name)
{
    if (!visitedFiles.insert(name).second)
    {
        return;   // already existed (did not insert)
    }

    // use stdio and buffering within Coco/R -- (faster)
    FILE *fh = fopen(name.c_str(), "r");
    if (fh)
    {
        std::cout << depFile << ": " << name << "\n";
    }
    else
    {
        for
        (
            std::list<std::string>::const_iterator iter = includeDirs.begin();
            iter != includeDirs.end();
            ++iter
        )
        {
            const std::string pathName = *iter + name;

            fh = fopen(pathName.c_str(), "r");
            if (fh)
            {
                std::cout << depFile << ": " << pathName << "\n";
                break;
            }
        }
    }

    if (fh)
    {
        Scanner scanner(fh);
        Parser  parser(&scanner);

        parser.Parse();
        fclose(fh);
    }
    else
    {
        fwprintf
        (
            stderr,
            L"could not open file %s for source file %s\n",
            name.c_str(), sourceFile.c_str()
        );

        // only report the first occurance
        visitedFiles.insert(name);
    }
}


void Parser::importFile(const std::string& name)
{
    // check if a globbed form was already visited
    std::string::size_type dotPos = name.find('.');
    if (dotPos != std::string::npos)
    {
        std::string dirGlob = name.substr(0, dotPos);
        dirGlob += ".*";

        if (visitedDirs_.find(dirGlob) != visitedDirs_.end())
        {
            return;
        }
    }

    std::string javaFileName = name;

    dotToSlash(javaFileName);
    javaFileName += ".java";

    includeFile(javaFileName);
}


void Parser::importDir(const std::string& name)
{
    if (!visitedDirs_.insert(name).second)
    {
        return;   // already existed (did not insert)
    }

    std::string dirName = name;
    dotToSlash(dirName);

    DIR *source = opendir(dirName.c_str());

    if (source)
    {
        struct dirent *list;

        // Read and parse all the entries in the directory
        while ((list = readdir(source)) != NULL)
        {
            const char* ext = strstr(list->d_name, ".java");

            // avoid matching on something like '.java~'
            if (ext && strlen(ext) == 5)
            {
                std::string pathName = dirName + list->d_name;
                includeFile(pathName);
            }
        }

        closedir(source);
    }
    else
    {
        fwprintf
        (
            stderr,
            L"could not open directory %s\n",
            dirName.c_str()
        );
        return;
    }
}

[/code]


/*---------------------------------------------------------------------------*/

CHARACTERS
    letter    = 'A'..'Z' + 'a'..'z' + '_'.
    digit     = "0123456789".
    cr        = '\r'.
    lf        = '\n'.
    tab       = '\t'.
    stringCh  = ANY - '"' - '\\' - cr - lf.
    printable = '\u0020' .. '\u007e'.
    java_letter = letter + '$'.

// * * * * * * * * * * * * * * * *  TOKENS * * * * * * * * * * * * * * * * * //

TOKENS

// string
string =
    '"' { stringCh | '\\' printable } '"'.

// single-quoted string (eg, Fortran)
sqstring =
    '\'' { stringCh | '\\' printable } '\''.

// for java import
package_name =
    java_letter { java_letter | digit }
    { '.' java_letter { java_letter | digit } } .

// for java import
package_dir =
    java_letter { java_letter | digit }
    { '.' java_letter { java_letter | digit } } ".*" .


// * * * * * * * * * * *  PRAGMAS / COMMENTS / IGNORE  * * * * * * * * * * * //

COMMENTS FROM "/*" TO "*/" NESTED
COMMENTS FROM "//" TO lf

IGNORE tab

// * * * * * * * * * * * * * * *  PRODUCTIONS  * * * * * * * * * * * * * * * //

PRODUCTIONS

wmkdepend
=
{
    // C/C++-style includes
    '#'
    [
        "include"
        [
            string      (.
                           if (isUTF8())
                           {
                               includeFile(t->toStringUTF8(1, t->length()-2));
                           }
                           else
                           {
                               includeFile(t->toString(1, t->length()-2));
                           }
                         .)
        ]
    ]
    [ ANY { ANY } ] '\n'    // skip trailing junk

    // Fortran-style includes
  | "include"
    [
        sqstring        (.
                           if (isUTF8())
                           {
                               includeFile(t->toStringUTF8(1, t->length()-2));
                           }
                           else
                           {
                               includeFile(t->toString(1, t->length()-2));
                           }
                         .)
    ]
    [ ANY { ANY } ] '\n'    // skip trailing junk

        // Java imports
  | "import"
    (
        package_dir     (.
                           if (isUTF8())
                           {
                               importDir(t->toStringUTF8());
                           }
                           else
                           {
                               importDir(t->toString());
                           }
                         .)
      | package_name    (.
                           if (isUTF8())
                           {
                               importFile(t->toStringUTF8());
                           }
                           else
                           {
                               importFile(t->toString());
                           }
                         .)
    )
    ';'
    [ ANY { ANY } ] '\n'    // skip trailing junk

  | [ ANY { ANY } ] '\n'    // skip any other lines

}
.


/*---------------------------------------------------------------------------*/

END wmkdepend.

// ************************************************************************* //
